package pl.edu.agh.jemo.evolution.selections.impl;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.JPanel;

import org.apache.log4j.Logger;

import pl.edu.agh.jemo.evolution.common.JemoRandom;
import pl.edu.agh.jemo.evolution.population.Population;
import pl.edu.agh.jemo.evolution.selections.Tournament;
import pl.edu.agh.jemo.evolution.specimen.Specimen;
import pl.edu.agh.jemo.gui.LoggerHelper;
import pl.edu.agh.jemo.gui.panels.selection.ClassicTournamentConfigPanel;

/**
 * Random based tournament implementation. Allows to modify parameters giving more control on result population.
 * New specimen are chosen in two steps. In first step, subset of specimen is selected and sorted by fitness.
 * In second step, new specimen is chosen. Every weaker specimen has considerably lower chance of being added to result population.
 * After each run one winer is selected, until expected number of winners is found.
 * 
 * @author Marcin Tkacz, Marcin Zbijowski
 *
 */
public class ClassicTournament extends Tournament {

	Logger logger = LoggerHelper.getInstance().getLogger();
	/**
	 * Number of specimen used in tournament of population subset.
	 */
	protected double tournamentSizeRatio = .8;
	protected int tournamentSize;
	/**
	 * Chance for choosing first specimen in second stage of tournament.
	 */
	protected double probability = .8;
		
	/**
	 * Chooses new specimen set based on input population.
	 * Performs two steps of algorithm, until return population is filled with specimen.
	 * In first step chooses random subset of given size and in second step selects winner from chosen subset.
	 * Winner clone is added to return population.
	 * 
	 * @param competitors Input population, on which selection is performed.
	 */
	@Override
	public void performTournament(Population competitors) {
		logger.debug(String.format("Tournament started. Expected population: %d , competitors size: %d", expectedPopulationSize, competitors.size()));
		tournamentSize = (int) (competitors.size()*tournamentSizeRatio);
		logger.debug(String.format("Single tournament size %d", tournamentSize));
		final Population toReturn = new Population();
		while (toReturn.size() < expectedPopulationSize) {
						
			final List<Specimen> contestants = getSingleTournamentContestants(competitors);
			final Specimen winner = performSingleTournament(contestants);
			toReturn.add(winner.clone());
			logger.debug(String.format("winner: %s", winner));
		}
		competitors.clear();
		competitors.addAll(toReturn);
	}
	
	/**
	 * Chooses subset of population to take part in one tournament.
	 * Number of specimen chosen is set by class attribute. Specimen are selected randomly, but it is assured the same specimen won't be picked twice.
	 * 
	 * @param competitors Population from which specimen are selected.
	 * @return List of specimen chosen for tournament, sorted by fitness.
	 */
	private List<Specimen> getSingleTournamentContestants(Population competitors) {
		final Population toReturn = new Population();
		final Set<Integer> chosen = new HashSet<Integer>();
 		for (int i = 0; i < tournamentSize; i++) {
			int index = JemoRandom.getRandom().nextInt(competitors.size());
			while (chosen.contains(index)) {
				index = JemoRandom.getRandom().nextInt(competitors.size());
			}
			chosen.add(index);
			toReturn.add(competitors.get(index));
		}
 		logger.debug(String.format("single tournament done"));
		Collections.sort(toReturn, Population.getFitnessComparator());
		return toReturn;
	}
	
	/**
	 * Finds winner of the tournament from specimen given in input parameter.
	 * Takes winner with chance given in attribute. If that fails, tries to pick second best specimen and so on, until one specimen is picked.
	 * 
	 * @param contestants List of specimen, from which winner is chosen.
	 * @return Reference to winner of tournament.
	 */
	private Specimen performSingleTournament(List<Specimen> contestants) {
		
		double[] probs = new double[tournamentSize];
		probs[0] = probability;
		for (int i = 1; i < tournamentSize; i++) {
				probs[i] = probs[i-1] * (1. - probability);
		}
		double shot = JemoRandom.getRandom().nextDouble();
		double temp = probs[0];
		int chosenSpecimenIndex = 0;
		while (shot < temp && chosenSpecimenIndex < tournamentSize) {
			temp += probs[chosenSpecimenIndex];
			chosenSpecimenIndex++;
		}
		if (chosenSpecimenIndex >= tournamentSize) {
			return contestants.get(0);
		}
		return contestants.get(chosenSpecimenIndex);
	}

	/**
	 * Returns size of single tournament run.
	 * 
	 * @return Number of specimen in single tournament.
	 */
	public double getTournamentSize() {
		return tournamentSizeRatio;
	}

	/**
	 * Sets size of single tournament run.
	 * 
	 * @param tournamentSize New number of specimen in single tournament.
	 */
	public void setTournamentSizeRatio(double tournamentSize) {
		this.tournamentSizeRatio = tournamentSize;
	}

	/**
	 * Returns chance of choosing best specimen from tournament.
	 * 
	 * @return Double from 0. to 1. range representing chance of choosing specimen.
	 */
	public double getProbability() {
		return probability;
	}

	/**
	 * Sets chance of choosing best specimen from tournament.
	 * 
	 * @param probability New double from 0. to 1. range representing chance of choosing specimen.
	 */
	public void setProbability(double probability) {
		this.probability = probability;
	}	
	
	/**
	 * Returns configuration panel being extension of JPanel instance.
	 * Returned panel is displayed by graphical user interface allowing user to confgiure tournament.
	 * This method may return null if no configuration for operator is possible.
	 * 
	 * @return Instance of JPanel with initialized components prepared to configure tournament properties.
	 */
	@Override
	public JPanel getConfigPanel() {
		return new ClassicTournamentConfigPanel(this);
	}
	
	/**
	 * Returns String object containing description of tournament configuration in readable form as a summary.
	 * 
	 * @return String description of tournament configuration.
	 */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(super.toString());
		sb.append("Tournament size ratio: " + tournamentSizeRatio + "\n");
		sb.append("Probability: " + probability + "\n");
		return sb.toString();
	}

}
